﻿\myparagraph{32-битный ARM}

\mysubparagraph{\NonOptimizingXcodeIV (\ARMMode)}

\lstinputlisting[caption=\NonOptimizingXcodeIV (\ARMMode),label=ARM_leaf_example7,style=customasmARM]{patterns/10_strings/1_strlen/ARM/xcode_ARM_O0_RU.asm}

Неоптимизирующий LLVM генерирует слишком много кода. Зато на этом примере можно посмотреть, 
как функции работают с локальными переменными в стеке.

В нашей функции только локальных переменных две --- это два указателя:
\IT{eos} и \IT{str}.
В этом листинге сгенерированном при помощи \IDA мы переименовали \IT{var\_8} и \IT{var\_4} в \IT{eos} и \IT{str} вручную.%

Итак, первые несколько инструкций просто сохраняют входное значение в обоих переменных \IT{str} и \IT{eos}.

С метки \IT{loc\_2CB8} начинается тело цикла.

Первые три инструкции в теле цикла (\TT{LDR}, \ADD, \TT{STR}) 
загружают значение \IT{eos} в \Reg{0}. 
Затем происходит инкремент значения и оно сохраняется в локальной переменной \IT{eos} расположенной 
в стеке.

\myindex{ARM!\Instructions!LDRSB}
Следующая инструкция \TT{LDRSB R0, [R0]} (\q{Load Register Signed Byte}) 
загружает байт из памяти по адресу \Reg{0}, расширяет его до 32-бит считая его знаковым (signed) 
и сохраняет в \Reg{0}
\footnote{Компилятор Keil считает тип \Tchar знаковым, как и MSVC и GCC.}.
\myindex{x86!\Instructions!MOVSX}
Это немного похоже на инструкцию \MOVSX в x86.
Компилятор считает этот байт знаковым (signed), потому что тип \Tchar по стандарту Си~--- знаковый.

Об этом уже было немного написано~(\myref{MOVSX}) в этой же секции, но посвященной x86.

\myindex{Intel!8086}
\myindex{Intel!8080}
\myindex{ARM}
Следует также заметить, что в ARM нет возможности использовать 8-битную или 16-битную часть 
регистра, как это возможно в x86.

Вероятно, это связано с тем, что за x86 тянется длинный шлейф совместимости со своими предками, 
вплоть до 16-битного 8086 и даже 8-битного 8080, 
а ARM разрабатывался с чистого листа как 32-битный RISC-процессор.

Следовательно, чтобы работать с отдельными байтами на ARM, так или иначе придется использовать 
32-битные регистры.

Итак, \TT{LDRSB} загружает символы из строки в \Reg{0}, по одному.

Следующие инструкции \CMP и \ac{BEQ} проверяют, является ли этот символ 0.

Если не 0, то происходит переход на начало тела цикла.
А если 0, выходим из цикла.

В конце функции вычисляется разница между 
\IT{eos} и \IT{str}, вычитается единица, и вычисленное 
значение возвращается через \Reg{0}.

N.B. В этой функции не сохранялись регистры.
\myindex{ARM!\Registers!scratch registers}
По стандарту регистры \Reg{0}-\Reg{3} называются также \q{scratch registers}.
Они предназначены для передачи аргументов и 
их значения не нужно восстанавливать при выходе из функции, потому что они больше не нужны в вызывающей функции.
Таким образом, их можно использовать как захочется.

А~так~как никакие больше регистры не используются, то и сохранять нечего.

Поэтому управление можно вернуть вызывающей функции 
простым переходом (\TT{BX}) по адресу в регистре \ac{LR}.

\mysubparagraph{\OptimizingXcodeIV (\ThumbMode)}

\lstinputlisting[caption=\OptimizingXcodeIV (\ThumbMode),style=customasmARM]{patterns/10_strings/1_strlen/ARM/xcode_thumb_O3.asm}

Оптимизирующий LLVM решил, что под переменные \IT{eos} и \IT{str} выделять место в стеке не обязательно,
и эти переменные можно хранить прямо в регистрах.

Перед началом тела цикла \IT{str} будет находиться в \Reg{0}, 
а \IT{eos} --- в \Reg{1}.

\myindex{ARM!\Instructions!LDRB.W}
Инструкция \TT{LDRB.W R2, [R1],\#1} загружает в \Reg{2} байт из памяти по адресу \Reg{1}, 
расширяя его как знаковый (signed), до 32-битного
значения, но не только это.

\TT{\#1} в конце инструкции означает \q{Post-indexed addressing},
т.е. после загрузки байта к \Reg{1} добавится единица.

Читайте больше об этом: \myref{ARM_postindex_vs_preindex}.

Далее в теле цикла можно увидеть \CMP и \ac{BNE}. Они продолжают работу цикла до тех пор, 
пока не будет встречен 0.

\myindex{ARM!\Instructions!MVNS}
\myindex{x86!\Instructions!NOT}
После конца цикла \TT{MVNS}\footnote{MoVe Not} (инвертирование всех бит, \NOT в x86) и \ADD вычисляют $eos - str - 1$.
На самом деле, эти две инструкции вычисляют $R0 = ~str + eos$, 
что эквивалентно тому, что было в исходном коде. Почему это так, уже было описано чуть раньше, здесь 
~(\myref{strlen_NOT_ADD}).

Вероятно, LLVM, как и GCC, посчитал, что такой код может быть короче (или быстрее).

\mysubparagraph{\OptimizingKeilVI (\ARMMode)}

\lstinputlisting[caption=\OptimizingKeilVI (\ARMMode),label=ARM_leaf_example6,style=customasmARM]{patterns/10_strings/1_strlen/ARM/Keil_ARM_O3.asm}

\myindex{ARM!\Instructions!SUBEQ}
Практически то же самое, что мы уже видели, за тем исключением, что выражение $str - eos - 1$ 
может быть вычислено не в самом конце функции, а прямо в теле цикла.

Суффикс \TT{-EQ} означает, что инструкция будет выполнена только
если операнды в исполненной перед этим инструкции \CMP были равны.

Таким образом, если в \Reg{0} будет 0,
обе инструкции \TT{SUBEQ} исполнятся и результат останется в \Reg{0}.

